# -*-coding=utf8-*-

import socket, sys, time
from urllib.parse import urlparse
# from common.utils.utils_re  import re_mail,re_mobile,isdigit
from .utils_re import re_mail, re_mobile, isdigit, is_domain, is_ipv4, is_url

import logging

logging.basicConfig(filename='checkport.log',
                    format='%(asctime)s -%(name)s-%(levelname)s-%(module)s:%(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S %p',
                    level=logging.DEBUG)


class CheckAlive(object):
    """
        check whether remote service is alive or not
    """
    allowed_protocol = {'TCP': socket.SOCK_STREAM, 'UDP': socket.SOCK_DGRAM}
    allowed_timeout = {'min': 0.2, 'mid': 0.5, 'max': 3}
    interval_time = 2
    attempt_count = 5

    def __init__(self, interval=0, attempt=0):
        """
        :param interval: 设置重试等待时间间隔，默认为 interval_time = 2
        :param attempt:  设置最大尝试次数，默认为 attempt_count
        最大等待时间为：interval_time*5 * attempt_count*5 = 250秒
        """
        # if  isdigit(interval):
        #     _interval = int(interval)
        #     self.init_interval(_interval)
        #
        # if  isdigit(attempt):
        #     self.init_attempt(int(attempt))
        # print(CheckAlive.interval_time,type(CheckAlive.interval_time),int(CheckAlive.interval_time))
        # print(CheckAlive.attempt_count, type(CheckAlive.attempt_count),int(CheckAlive.attempt_count))

        self.interval_time = interval
        self.attempt_count = attempt

    @property
    def interval_time(self):
        return self.interval_time

    @property
    def attempt_count(self):
        return self.attempt_count

    @interval_time.setter
    def interval_time(self, interval):
        if not isinstance(interval, (int,)):
            self.interval_time = int(CheckAlive.interval_time)
        elif interval < 0:
            self.interval_time = int(CheckAlive.interval_time)
        elif interval > int(CheckAlive.interval_time) * 5:
            self.interval_time = int(CheckAlive.interval_time) * 5
        else:
            self.interval_time = interval

    @attempt_count.setter
    def attempt_count(self, attempt):
        if not isinstance(attempt, (int,)):
            self.attempt_time = CheckAlive.attempt_time
        elif attempt < 0:
            self.attempt_time = CheckAlive.attempt_time
        elif attempt > CheckAlive.attempt_time * 5:
            self.attempt_time = CheckAlive.attempt_time * 5
        else:
            self.attempt_time = attempt

    # def init_interval(self, interval):
    #     if interval < CheckAlive.interval_time:
    #         pass
    #     elif interval > seCheckAlivelf.interval_time * 5:
    #         self.interval_time = CheckAlive.interval_time * 5
    #     else:
    #         self.interval_time = interval
    #
    #
    # def init_attempt(self, attempt):
    #     if attempt < CheckAlive.attempt_time:
    #         pass
    #     elif attempt > seCheckAlivelf.attempt_time * 5:
    #         self.attempt_time = CheckAlive.attempt_time * 5
    #     else:
    #         self.attempt_time = attempt

    def execute(self, **kwargs):
        """
        :param kwargs:
                    url = 域名，必填
                    port = 端口，必填
                    proto = TCP或UDP，选填，默认TCP
                    timeout = socket.timeout，选填 默认 allowed_timeout
        :return:
        """
        try:
            params = self.check_params(**kwargs)

            params = self.set_params_ip(**params)

            self.attempts(**params)

        except Exception as e:
            message = "execute:{0}".format(str(e))
            print(message)

    def set_params_ip(self, **params):
        """
        :param params: ip added to params
        :return: new params
        """
        _url = params.get('url')

        try:
            if self.check_ipaddress(_url):
                params['ip'] = _url
                return params

            elif self.check_domain(_url):
                _domain = self.check_domain(_url)
                params['ip'] = self.get_ip(_domain)
                return params

            elif self.check_urlnetloc(_url):
                _domain = self.check_urlnetloc(_url)
                params['ip'] = self.get_ip(_domain)
                return params

            else:
                raise Exception("url is not valid params:{0}".format(str(_url)))

        except Exception as e:
            message = "set_params_ip Failed:{0}".format(str(e))
            print(message)
            raise Exception(message)

    def get_ip(self, host):
        """
        :param host: IP or Domain
        :return: IP
        """
        if self.check_ipaddress(host):
            return self.check_ipaddress(host)

        elif self.check_domain(host):
            for i in range(3):
                try:
                    _ip = self.get_domain_ip(self.check_domain(host))
                    return _ip
                except:
                    _timeout = min(self.interval_time, 2)
                    time.sleep(_timeout)
                    continue
            else:
                message = "Domain Name Resolution Failed:{0}".format(str(host))
                print(message)
                raise Exception(message)

        else:
            raise Exception("input params is not IP or Domain:{0}".format(str(host)))

    # def get_domain(self, url):
    #     """
    #     :param url: domain or url
    #     :return: domain or ip
    #     """
    #     if  self.check_domain(url):
    #         return self.check_domain(url)
    #
    #     elif self.check_urlnetloc(url):
    #         _netloc =  self.check_urlnetloc(url)
    #
    #         if  self.check_domain(_netloc):
    #             return self.check_domain(_netloc)
    #         else:
    #             return
    #     else:
    #         return

    def attempts(self, **params):
        """
        appempt conncet , test service status
        :param params:
        :return:
        """
        n = 0

        while n < self.attempt_count:
            try:
                sock = self.generate_socket(**params)

            except Exception as e:
                message = "generate_socket:{0}".format(str(e))
                print(message)
                raise Exception(message)

            try:
                statuscode = self.check_service(sock, **params)
                if statuscode == 0:
                    sock.close()
                    break
                else:
                    sock.close()
                    n += 1
                    time.sleep(self.interval_time)

            except Exception as e:
                sock.close()
                message = "check_service:{0}".format(str(e))
                print(message)
                raise Exception(message)

    def get_domain_ip(self, domain):
        """
        :param domain:  domain
        :return:  ip
        """
        try:
            ip = socket.gethostbyname(domain)
            if ip:
                return ip
            else:
                message = 'invalid domain: {0}'.format(str(domain))
                print(message)
                raise Exception(message)

        except  Exception as e:
            message = 'get domain ip error: {0}'.format(str(e))
            print(message)
            raise

    def check_domain(self, domain):
        """
        :param domain: domain
        :return:  domain or None
        """
        if is_domain(domain):
            return domain
        else:
            return

    def check_urlnetloc(self, url):
        """
        :param url: url
        :return:  url.netloc: domain or ip
        """
        if is_url(url):
            parsed = urlparse(url)
        else:
            url = 'http://' + url
            parsed = urlparse(url)

        if parsed.netloc:
            _netloc_list = parsed.netloc.split(':')
            if _netloc_list:
                return _netloc_list[0]
            else:
                raise Exception("resolve params is invalid url:{0}".format(str(url)))
        else:
            raise Exception("input params is invalid url:{0}".format(str(url)))

    def check_ipaddress(self, ip):
        """
        :param ip:  ip
        :return:   ip or None
        """
        if is_ipv4(ip):
            return ip
        else:
            return

    def check_params(self, **kwargs):
        """
        :param kwargs:  url: IP、Domain、Url,must be selected
                        port: 1-65535,must be selected
                        proto: TCP or UDP,optional
                        timeout: S,optional
        :return: checked params
        """
        _url = kwargs.get('url')
        _port = kwargs.get('port')
        _proto = kwargs.get('proto')
        _timeout = kwargs.get('timeout')

        if _url and isinstance(_url, (str,)):
            kwargs['url'] = _url.strip()
        else:
            message = 'need input invalid url'
            raise Exception(message)

        if isdigit(_port):
            _port = int(_port)

            if _port < 1 or _port > 65535:
                message = 'port invalid: {0}'.format(str(_port))
                raise Exception(message)
            kwargs['port'] = _port

        else:
            message = 'need input port(1 - 65535),error:{0}'.format(str(_port))
            raise Exception(message)

        if isinstance(_proto, (str,)):
            _proto = _proto.strip().upper()
            _type = self.allowed_protocol.get('_proto') if self.allowed_protocol.get('_proto') else socket.SOCK_STREAM
            kwargs['type'] = _type

        else:
            kwargs['type'] = socket.SOCK_STREAM

        if isdigit(_timeout):
            _timeout = float(_timeout)
            if _timeout > self.allowed_timeout.get('max'):
                kwargs['timeout'] = self.allowed_timeout.get('max')

            elif _timeout < self.allowed_timeout.get('min'):
                kwargs['timeout'] = self.allowed_timeout.get('min')

            else:
                kwargs['timeout'] = _timeout

        else:
            kwargs['timeout'] = self.allowed_timeout.get('mid')

        return kwargs

    def generate_socket(self, **params):
        """
        :param params: generate socket instance
        :return: instance
        """
        _family = socket.AF_INET
        _type = params.get('type')
        _timeout = params.get('timeout')

        sock = socket.socket(_family, _type)
        sock.settimeout(_timeout)

        return sock

    def check_service(self, sock, **params):
        """
        :param sock: socket instance
        :param params:  params
        :return:  statuscode
        """
        _sock = sock

        if not params.get("ip", None) and self.check_ipaddress(params.get('url')):
            params['ip'] = self.check_ipaddress(params.get('url'))
        else:
            raise Exception("no parameter ip found")

        if not params.get("port", None):
            raise Exception("no parameter port found")

        _ip_port = (params.get('ip'), params.get('port'))

        try:
            statuscode = _sock.connect_ex(_ip_port)

        except Exception as e:
            message = "check_service error:{0}".format(str(e))
            print(message)
            sock.close()
            raise Exception(message)

        if statuscode == 0:
            print("service is running !")
        else:
            print("service isn't working correctly !")

        return statuscode


if __name__ == '__main__':

    chkalive = CheckAlive()
    print(dir(chkalive))
    print(chkalive.__dict__)

    kwargs = dict()
    kwargs['url'] = '10.100.19.172'
    kwargs['port'] = 80
    kwargs['timeout'] = 5
    params = chkalive.check_params(**kwargs)
    print('params: ', params)
    print("=====set params=====")
    try:
        params = chkalive.set_params_ip(**params)
    except Exception as e:
        print(str(e))
    print('params: ', params)

    print("=====attempt 10.100.19.172 =====")
    chkalive.attempts(**params)
    print("=====attempt =====")
    chkalive.attempts(**params)
    print("=====attempt =====")
    chkalive.attempts(**params)
